#include "lift_task.h"

#include "application/map/lift/lift_manager.h"
#include "application/dispatcher/dispatcher_service.h"
#include "kernel/agv/agv_entity.h"

#include <utility>

namespace application {
namespace task {

LiftTask::LiftTask(
    std::shared_ptr<vehicle::VehicleService> vehicleService,
    std::shared_ptr<MapApplicationService> mapService,
    std::shared_ptr<dispatcher::DispatcherService> dispatcherService,
    std::shared_ptr<kernel::ILogger> logger,
    int liftNum,
    int callFloor,
    int targetFloor)
    : SubTaskBase(std::move(logger))
    , m_vehicleService(std::move(vehicleService))
    , m_mapService(std::move(mapService))
    , m_dispatcherService(std::move(dispatcherService))
    , m_liftNum(liftNum)
    , m_callFloor(callFloor)
    , m_targetFloor(targetFloor)
{
}

kernel::task::TaskStage LiftTask::init(TaskContext& ctx)
{
    if (!ctx.agv) {
        return finishWithError(ctx, "No AGV assigned to lift task");
    }
    m_lastAgvNumber = ctx.agv->getNumber();

    bool liftUnavailable = false;
    bool askPointNotReached = false;
    bool targetWaitConflict = false;
    if (!requestLift(ctx, liftUnavailable, askPointNotReached, targetWaitConflict)) {
        if (askPointNotReached) {
            ctx.requestStatus(kernel::task::TaskChainStatus::Recalculation);
            return kernel::task::TaskStage::Init;
        }
        if (liftUnavailable) {
            const std::string reason = targetWaitConflict
                ? "无法预占目标楼层等待区"
                : "被占用，等待释放";
            const int agvNum = ctx.agv ? ctx.agv->getNumber() : -1;
            logWarning("电梯" + std::to_string(m_liftNum) + " " + reason +
                       "，AGV " + std::to_string(agvNum));
            return kernel::task::TaskStage::Init;
        }
        return finishWithError(
            ctx,
            "Failed to request lift " + std::to_string(m_liftNum));
    }

    ctx.setLiftHandle(m_lift);

    m_liftCtx.vehicleService = m_vehicleService;
    m_liftCtx.mapService = m_mapService;
    m_liftCtx.lift = m_lift;
    m_liftCtx.callFloorInfo = m_callFloorInfo;
    m_liftCtx.targetFloorInfo = m_targetFloorInfo;
    m_liftCtx.callMapName = m_callMapName;
    m_liftCtx.targetMapName = m_targetMapName;
    m_liftCtx.liftNum = m_liftNum;
    m_liftCtx.callFloor = m_callFloor;
    m_liftCtx.targetFloor = m_targetFloor;

    m_strategy = createLiftStrategy(m_liftType, m_logger);
    if (!m_strategy) {
        return finishWithError(
            ctx,
            "Unsupported lift type for lift " + std::to_string(m_liftNum));
    }

    if (!m_strategy->init(m_liftCtx, ctx)) {
        return finishWithError(ctx, "Failed to initialise lift strategy");
    }

    // 电梯流程开始后，释放调度器对该车的路径控制/占用，避免继续占用等待点
    if (!m_dispatcherRoutesCleared) {
        releaseDispatcherRoutes(m_lastAgvNumber);
        m_dispatcherRoutesCleared = true;
    }

    logInfo("Lift task initialized: AGV " + std::to_string(ctx.agv->getNumber()) +
            ", lift " + std::to_string(m_liftNum) + ", " +
            std::to_string(m_callFloor) + " -> " + std::to_string(m_targetFloor));

    return kernel::task::TaskStage::Running;
}

kernel::task::TaskStage LiftTask::run(TaskContext& ctx)
{
    if (!ctx.agv || !m_lift) {
        return finishWithError(ctx, "AGV or lift handle lost during task");
    }

    if (!m_strategy) {
        return finishWithError(ctx, "Lift strategy was not initialised");
    }

    auto stage = m_strategy->run(ctx);
    if (stage == kernel::task::TaskStage::Cleanup) {
        return kernel::task::TaskStage::Cleanup;
    }

    return kernel::task::TaskStage::Running;
}

kernel::task::TaskStage LiftTask::cleanup(TaskContext& ctx)
{
    releaseTargetWaitArea(ctx);
    releaseCallAreas(ctx);

    if (m_lift) {
        bool released = false;
        if (m_mapService) {
            if (auto liftManager = m_mapService->getLiftManager()) {
                liftManager->releaseLift(m_liftNum);
                released = true;
            }
        }
        if (!released) {
            m_lift->release();
        }
    }

    if (m_strategy) {
        m_strategy->cleanup(ctx);
        m_strategy.reset();
    }

    m_lift.reset();
    m_liftCtx.lift.reset();
    ctx.clearLiftHandle();

    logDebug("Lift task cleanup completed");

    return kernel::task::TaskStage::Finished;
}

bool LiftTask::requestLift(TaskContext& ctx,
                           bool& liftUnavailable,
                           bool& askPointNotReached,
                           bool& targetWaitConflict)
{
    liftUnavailable = false;
    askPointNotReached = false;
    targetWaitConflict = false;

    if (!m_mapService) {
        logError("Map service not available");
        return false;
    }

    auto liftManager = m_mapService->getLiftManager();
    if (!liftManager) {
        logError("Lift manager not available");
        return false;
    }

    auto liftPtr = liftManager->getLift(m_liftNum);
    if (!liftPtr) {
        logError("Lift not found: " + std::to_string(m_liftNum));
        return false;
    }

    if (liftPtr->getType() == lift::LiftType::MKLM) {
        logError("MKLM lift type is not supported");
        return false;
    }

    m_callFloorInfo = liftManager->getFloorInfo(m_liftNum, m_callFloor);
    m_targetFloorInfo = liftManager->getFloorInfo(m_liftNum, m_targetFloor);

    auto resolveMapName = [&](QString& outMapName, int floor) {
        if (!m_mapService) {
            return;
        }
        const QString foundMapName = m_mapService->getMapNameForFloor(floor);
        if (foundMapName.isEmpty()) {
            logWarning("No map name found for floor " + std::to_string(floor));
        }
        outMapName = foundMapName;
    };

    resolveMapName(m_callMapName, m_callFloor);
    resolveMapName(m_targetMapName, m_targetFloor);

    if (!ensureCallAreaReservations(ctx)) {
        liftUnavailable = true;
        return false;
    }

    auto isAtStation = [&](const QString& station) -> bool {
        if (station.isEmpty()) {
            return true;
        }
        if (!ctx.agv) {
            return false;
        }
        return ctx.agv->getCurrentStation() == station.toStdString();
    };

    if (!isAtStation(m_callFloorInfo.askPoint)) {
        askPointNotReached = true;
        logInfo(QString("AGV %1 not at lift %2 ask point %3, requesting recalculation")
                    .arg(ctx.agv ? ctx.agv->getNumber() : -1)
                    .arg(m_liftNum)
                    .arg(m_callFloorInfo.askPoint)
                    .toStdString());
        return false;
    }

    releaseCallWaitArea(ctx);

    auto requested = liftManager->requestLift(m_liftNum);
    if (!requested) {
        liftUnavailable = true;
        logWarning("Lift is unavailable (locked): " + std::to_string(m_liftNum));
        return false;
    }

    if (!reserveTargetWaitArea(ctx)) {
        liftUnavailable = true;
        targetWaitConflict = true;
        liftManager->releaseLift(m_liftNum);
        return false;
    }

    m_liftCtx = LiftContext{};
    m_lift = requested;
    m_liftType = m_lift->getType();

    lift::LiftRequestInfo requestInfo;
    requestInfo.liftNum = m_liftNum;
    requestInfo.startFloor = m_callFloorInfo;
    requestInfo.targetFloor = m_targetFloorInfo;
    requestInfo.agvId = ctx.agv ? ctx.agv->getNumber() : -1;

    if (!m_lift->request(requestInfo)) {
        releaseTargetWaitArea(ctx);
        if (m_mapService) {
            if (auto releaseManager = m_mapService->getLiftManager()) {
                releaseManager->releaseLift(m_liftNum);
            }
        }
        m_lift.reset();
        logError("Lift request initialisation failed for lift " +
                 std::to_string(m_liftNum));
        return false;
    }

    return true;
}

void LiftTask::releaseDispatcherRoutes(int agvNumber)
{
    if (!m_dispatcherService || agvNumber < 0) {
        return;
    }
    m_dispatcherService->cancelPendingForAgv(agvNumber);
    logInfo("Lift task: released dispatcher routes/cache for AGV " + std::to_string(agvNumber));
}

bool LiftTask::ensureCallAreaReservations(TaskContext& ctx)
{
    if (m_callAreasPrepared) {
        return true;
    }

    bool success = true;
    if (!m_callFloorInfo.askAreaId.isEmpty()) {
        success = occupyArea(m_callFloorInfo.askAreaId,
                             m_callFloor,
                             m_callAskAreaOccupied,
                             "call ask",
                             ctx);
        if (!success) {
            return false;
        }
    }

    if (!m_callFloorInfo.waitAreaId.isEmpty()) {
        success = occupyArea(m_callFloorInfo.waitAreaId,
                             m_callFloor,
                             m_callWaitAreaOccupied,
                             "call wait",
                             ctx);
        if (!success) {
            releaseArea(m_callFloorInfo.askAreaId,
                        m_callFloor,
                        m_callAskAreaOccupied,
                        "call ask",
                        ctx);
            return false;
        }
    }

    m_callAreasPrepared = true;
    return true;
}

bool LiftTask::reserveTargetWaitArea(TaskContext& ctx)
{
    if (m_targetFloorInfo.waitAreaId.isEmpty()) {
        return true;
    }

    return occupyArea(m_targetFloorInfo.waitAreaId,
                      m_targetFloor,
                      m_targetWaitAreaOccupied,
                      "target wait",
                      ctx);
}

bool LiftTask::occupyArea(const QString& areaId,
                          int floor,
                          bool& occupiedFlag,
                          const char* label,
                          TaskContext& ctx)
{
    if (areaId.isEmpty() || occupiedFlag) {
        return true;
    }

    int agvNumber = -1;
    if (ctx.agv) {
        agvNumber = ctx.agv->getNumber();
    } else if (m_lastAgvNumber >= 0) {
        agvNumber = m_lastAgvNumber;
    }

    if (agvNumber < 0) {
        logWarning(std::string("Cannot occupy ") + label + " area without AGV number");
        return false;
    }
    m_lastAgvNumber = agvNumber;

    const std::string areaStd = areaId.toStdString();
    bool success = false;

    if (m_dispatcherService) {
        success = m_dispatcherService->occupyArea(areaStd, agvNumber, floor);
    } else if (m_mapService) {
        if (auto areaMgr = m_mapService->getAreaManager(floor)) {
            if (areaMgr->hasArea(areaId)) {
                areaMgr->enterArea(areaId, agvNumber);
                success = true;
                ctx.addOccupiedArea(areaMgr->getArea(areaId));
            }
        }
    }

    if (success) {
        occupiedFlag = true;
        if (m_mapService) {
            if (auto areaMgr = m_mapService->getAreaManager(floor)) {
                if (areaMgr->hasArea(areaId)) {
                    ctx.addOccupiedArea(areaMgr->getArea(areaId));
                }
            }
        }
        logDebug(QString("AGV %1 occupied %2 area %3 on floor %4")
                     .arg(agvNumber)
                     .arg(label)
                     .arg(areaId)
                     .arg(floor)
                     .toStdString());
        return true;
    }

    logDebug(QString("Failed to occupy %1 area %2 on floor %3")
                 .arg(label)
                 .arg(areaId)
                 .arg(floor)
                 .toStdString());
    return false;
}

void LiftTask::releaseArea(const QString& areaId,
                           int floor,
                           bool& occupiedFlag,
                           const char* label,
                           TaskContext& ctx)
{
    if (areaId.isEmpty() || !occupiedFlag) {
        return;
    }

    int agvNumber = -1;
    if (ctx.agv) {
        agvNumber = ctx.agv->getNumber();
    } else if (m_lastAgvNumber >= 0) {
        agvNumber = m_lastAgvNumber;
    }

    const std::string areaStd = areaId.toStdString();

    if (m_dispatcherService && agvNumber >= 0) {
        m_dispatcherService->releaseArea(areaStd, agvNumber, floor);
    } else if (m_mapService) {
        if (auto areaMgr = m_mapService->getAreaManager(floor)) {
            if (areaMgr->hasArea(areaId)) {
                if (agvNumber >= 0) {
                    areaMgr->exitArea(areaId, agvNumber);
                }
            }
        }
    }

    occupiedFlag = false;
    logDebug(QString("Released %1 area %2 on floor %3")
                 .arg(label)
                 .arg(areaId)
                 .arg(floor)
                 .toStdString());
}

void LiftTask::releaseCallAreas(TaskContext& ctx)
{
    releaseArea(m_callFloorInfo.askAreaId,
                m_callFloor,
                m_callAskAreaOccupied,
                "call ask",
                ctx);
    releaseArea(m_callFloorInfo.waitAreaId,
                m_callFloor,
                m_callWaitAreaOccupied,
                "call wait",
                ctx);
    m_callAreasPrepared = false;
}

void LiftTask::releaseCallWaitArea(TaskContext& ctx)
{
    releaseArea(m_callFloorInfo.waitAreaId,
                m_callFloor,
                m_callWaitAreaOccupied,
                "call wait",
                ctx);
}

void LiftTask::releaseTargetWaitArea(TaskContext& ctx)
{
    releaseArea(m_targetFloorInfo.waitAreaId,
                m_targetFloor,
                m_targetWaitAreaOccupied,
                "target wait",
                ctx);
}

} // namespace task
} // namespace application
